package nacos

import (
	"context"
	"time"

	"Hypnos-Soundcore/components/nacos/config"
	"github.com/nacos-group/nacos-sdk-go/clients"
	"github.com/nacos-group/nacos-sdk-go/clients/config_client"
	"github.com/nacos-group/nacos-sdk-go/common/constant"
	"github.com/nacos-group/nacos-sdk-go/vo"
)

var DefaultNacosConfigSource *Config

type Option func(*options)

type options struct {
	serverConfig []ServerConfig

	namespaceID string

	group  string
	dataID string

	timeoutMs uint64
	logLevel  string

	logDir   string
	cacheDir string

	notLoadCacheAtStart bool
	rotateTime          string

	userName string
	passWord string
}

type ServerConfig struct {
	IpAddr      string
	Port        uint64
	Scheme      string // Nacos的服务地址前缀
	ContextPath string // Nacos的ContextPath
}

// WithGroup With nacos config group.
func WithGroup(group string) Option {
	return func(o *options) {
		o.group = group
	}
}

// WithDataID With nacos config data id.
func WithDataID(dataID string) Option {
	return func(o *options) {
		o.dataID = dataID
	}
}

// WithLogDir With nacos config group.
func WithLogDir(logDir string) Option {
	return func(o *options) {
		o.logDir = logDir
	}
}

// WithCacheDir With nacos config cache dir.
func WithCacheDir(cacheDir string) Option {
	return func(o *options) {
		o.cacheDir = cacheDir
	}
}

// WithLogLevel With nacos config log level.
func WithLogLevel(logLevel string) Option {
	return func(o *options) {
		o.logLevel = logLevel
	}
}

// WithTimeout With nacos config timeout.
func WithTimeout(time time.Duration) Option {
	return func(o *options) {
		o.timeoutMs = uint64(time.Milliseconds())
	}
}

// WithNamespaceID With nacos config namespaceID.
func WithNamespaceID(namespaceID string) Option {
	return func(o *options) {
		o.namespaceID = namespaceID
	}
}

// WithUsername ...
func WithUsername(userName string) Option {
	return func(o *options) {
		o.userName = userName
	}
}

// WithPassword ...
func WithPassword(passWord string) Option {
	return func(o *options) {
		o.passWord = passWord
	}
}

// WithRotateTime With nacos config WithRotateTime.
func WithRotateTime(rotateTime string) Option {
	return func(o *options) {
		o.rotateTime = rotateTime
	}
}

// WithNotLoadCacheAtStart With nacos config notLoadCacheAtStart.
func WithNotLoadCacheAtStart(notLoadCacheAtStart bool) Option {
	return func(o *options) {
		o.notLoadCacheAtStart = notLoadCacheAtStart
	}
}

// WithServerConfig With nacos server config.
func WithServerConfig(serverConfig []ServerConfig) Option {
	return func(o *options) {
		o.serverConfig = serverConfig
	}
}

type Config struct {
	opts   options
	client config_client.IConfigClient
}

func NewIConfigClient(opts ...Option) (config_client.IConfigClient, error) {
	_options := options{}
	for _, o := range opts {
		o(&_options)
	}
	sc := make([]constant.ServerConfig, 0)
	for index, _ := range _options.serverConfig {
		sc = append(sc, constant.ServerConfig{
			Scheme:      _options.serverConfig[index].Scheme,
			ContextPath: _options.serverConfig[index].ContextPath,
			IpAddr:      _options.serverConfig[index].IpAddr,
			Port:        _options.serverConfig[index].Port,
		})
	}

	cc := constant.ClientConfig{
		NamespaceId:         _options.namespaceID, //namespace id
		TimeoutMs:           _options.timeoutMs,
		NotLoadCacheAtStart: _options.notLoadCacheAtStart,
		LogDir:              _options.logDir,
		CacheDir:            _options.cacheDir,
		LogLevel:            _options.logLevel,
		Username:            _options.userName,
		Password:            _options.passWord,
	}

	client, err := clients.NewConfigClient(
		vo.NacosClientParam{
			ClientConfig:  &cc,
			ServerConfigs: sc,
		},
	)

	return client, err
}

func NewConfigSource(client config_client.IConfigClient, opts ...Option) config.Source {
	_options := options{}
	for _, o := range opts {
		o(&_options)
	}
	return &Config{client: client, opts: _options}
}

func (c *Config) Load() ([]*config.KeyValue, error) {
	content, err := c.client.GetConfig(vo.ConfigParam{
		DataId: c.opts.dataID,
		Group:  c.opts.group,
	})
	if err != nil {
		return nil, err
	}

	return []*config.KeyValue{
		{
			Key:   c.opts.dataID,
			Value: []byte(content),
		},
	}, nil
}

func (c *Config) Watch() (config.Watcher, error) {
	watcher := newWatcher(context.Background(), c.opts.dataID, c.opts.group, c.client.CancelListenConfig)
	err := c.client.ListenConfig(vo.ConfigParam{
		DataId: c.opts.dataID,
		Group:  c.opts.group,
		OnChange: func(namespace, group, dataId, data string) {
			if dataId == watcher.dataID && group == watcher.group {
				watcher.content <- data
			}
		},
	})
	if err != nil {
		return nil, err
	}
	return watcher, nil
}

type Watcher struct {
	context.Context
	dataID             string
	group              string
	content            chan string
	cancelListenConfig cancelListenConfigFunc
	cancel             context.CancelFunc
}

type cancelListenConfigFunc func(params vo.ConfigParam) (err error)

func newWatcher(ctx context.Context, dataID string, group string, cancelListenConfig cancelListenConfigFunc) *Watcher {
	w := &Watcher{
		dataID:             dataID,
		group:              group,
		cancelListenConfig: cancelListenConfig,
		content:            make(chan string, 100),
	}
	ctx, cancel := context.WithCancel(ctx)
	w.Context = ctx
	w.cancel = cancel
	return w
}

func (w *Watcher) Next() ([]*config.KeyValue, error) {
	select {
	case <-w.Context.Done():
		return nil, nil
	case content := <-w.content:
		return []*config.KeyValue{
			{
				Key:   w.dataID,
				Value: []byte(content),
			},
		}, nil
	}
}

func (w *Watcher) Close() error {
	err := w.cancelListenConfig(vo.ConfigParam{
		DataId: w.dataID,
		Group:  w.group,
	})
	w.cancel()
	return err
}

func (w *Watcher) Stop() error {
	return w.Close()
}

func InitDefaultNacosConfigSource(client config_client.IConfigClient, opts ...Option) {
	_options := options{}
	for _, o := range opts {
		o(&_options)
	}
	DefaultNacosConfigSource = &Config{
		client: client,
		opts:   _options,
	}

}

func GetDefaultNacosConfigSource() *Config {
	return DefaultNacosConfigSource
}
